<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制菱形(包含边框)</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    #canvasBox {
      display: inherit;
      width: 800px;
      height: 502px;
      margin: 10px auto 0;
      border: 1px solid #000;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="canvasBox">
    <canvas id="myCanvas" width="100" height="100"></canvas>
  </div>

  <script>
    // 里程碑数据
    let MilestoneData = [
      {
        projectSeq: '201A',// 项目代号
        milestone: [
          // {
          //   name: '里程碑1里程碑1里程碑1里程碑1里程碑1里程碑1',// 里程碑名称
          //   description: '里程碑1描述',// 里程碑描述
          //   status: 1,// 里程碑状态
          //   plantime: '2023-02-20',// 里程碑开始时间
          // },
          // {
          //   name: '里程碑2',// 里程碑名称
          //   description: '里程碑2描述',// 里程碑描述
          //   status: 1,// 里程碑状态
          //   plantime: '2023-06-03',// 里程碑开始时间
          // },
          // {
          //   name: '里程碑3',// 里程碑名称
          //   description: '里程碑3描述',// 里程碑描述
          //   status: 1,// 里程碑状态
          //   plantime: '2023-11-12',// 里程碑开始时间
          // }
          {
            name: '里程碑1里程碑1里程碑1里程碑1里程碑1里程碑1',// 里程碑名称
            description: '里程碑1描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-07-01',// 里程碑开始时间
          },
          {
            name: '里程碑2',// 里程碑名称
            description: '里程碑2描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-08-31',// 里程碑开始时间
          },
          {
            name: '里程碑3',// 里程碑名称
            description: '里程碑3描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-09-30',// 里程碑开始时间
          },
          {
            name: '里程碑1里程碑1里程碑1里程碑1里程碑1里程碑1',// 里程碑名称
            description: '里程碑1描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-10-30',// 里程碑开始时间
          },
          {
            name: '里程碑2',// 里程碑名称
            description: '里程碑2描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-11-20',// 里程碑开始时间
          },
          {
            name: '里程碑3',// 里程碑名称
            description: '里程碑3描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-12-31',// 里程碑开始时间
          }
        ],
      },
      {
        projectSeq: '201B',// 项目代号
        milestone: [
          {
            name: 'boxue1',// 里程碑名称
            description: '里程碑1描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-09-20',// 里程碑开始时间
          },
          {
            name: '里程碑2',// 里程碑名称
            description: '里程碑2描述',// 里程碑描述
            status: 1,// 里程碑状态
            plantime: '2023-09-20',// 里程碑开始时间
          }
        ],
      },
      // {
      //   projectSeq: '201B',// 项目代号
      //   milestone: [
      //     {
      //       name: 'boxue1',// 里程碑名称
      //       description: '里程碑1描述',// 里程碑描述
      //       status: 1,// 里程碑状态
      //       plantime: '2023-02-10',// 里程碑开始时间
      //     },
      //     {
      //       name: '里程碑2',// 里程碑名称
      //       description: '里程碑2描述',// 里程碑描述
      //       status: 1,// 里程碑状态
      //       plantime: '2023-05-03',// 里程碑开始时间
      //     },
      //     {
      //       name: '里程碑3',// 里程碑名称
      //       description: '里程碑3描述',// 里程碑描述
      //       status: 1,// 里程碑状态
      //       plantime: '2023-07-05',// 里程碑开始时间
      //     },
      //     {
      //       name: '里程碑4',// 里程碑名称
      //       description: '里程碑3描述',// 里程碑描述
      //       status: 1,// 里程碑状态
      //       plantime: '2023-9-20',// 里程碑开始时间
      //     }
      //   ],
      // }
    ];
    MilestoneData = new Array(5).fill('').reduce((acc, v) => acc.concat(MilestoneData), []);
    console.log(`index.html 59[MilestoneData]`, MilestoneData, MilestoneData.length);
    let relationship = [
      {}
    ]// 节点之间的关联关系

    // 属性节点设置
    let nodeAttribute = [
      {
        status: 1,// 未完成未延期
        color: '#999999'
      }, {
        status: 2,// 未完成已延期
        color: '#ff5722'
      }, {
        status: 3,// 即将到期
        color: '#ffb800'
      }, {
        status: 4,// 已完成
        color: '#16baaa'
      }
    ];
  </script>

  <script>


    // ctx.strokeStyle = 'rgba(0, 0, 0, 1)';
    // ctx.lineWidth = 1; // 线条宽度



    /* 
    绘制菱形，要求输入某个点位中心点位置，以中心点绘制任意宽度，动态输入边框宽度，颜色，菱形背景色(要求背景色区域是宽度减去边框后的)，并在中心点绘制一个的圆点，并且菱形颜色和小球的颜色支持动态参数输入，在菱形正下方某个距离的地方可以输出文字，也可以不输，文字整体居中，写出该函数,函数参数是对象方式传入
    
    */
    // 绘制菱形
    function drawDiamond({ centerX, centerY, width, borderWidth, borderColor, diamondColor, dotColor, dotSize, text, textOffset }) {
      const canvas = document.getElementById('myCanvas');
      const ctx = canvas.getContext('2d');

      // 计算菱形四个顶点的坐标
      const halfWidth = width / 2;
      // const points = [
      //   { x: centerX, y: centerY - halfWidth },
      //   { x: centerX + halfWidth, y: centerY },
      //   { x: centerX, y: centerY + halfWidth },
      //   { x: centerX - halfWidth, y: centerY }
      // ];

      const points = [
        { x: centerX, y: centerY - halfWidth - borderWidth },
        { x: centerX + halfWidth + borderWidth, y: centerY },
        { x: centerX, y: centerY + halfWidth + borderWidth },
        { x: centerX - halfWidth - borderWidth, y: centerY }
      ];

      // 绘制菱形背景
      ctx.beginPath();
      ctx.moveTo(points[0].x, points[0].y);
      ctx.lineTo(points[1].x, points[1].y);
      ctx.lineTo(points[2].x, points[2].y);
      ctx.lineTo(points[3].x, points[3].y);
      ctx.closePath();
      ctx.fillStyle = diamondColor;
      ctx.fill();

      // 绘制菱形边框
      ctx.beginPath();
      ctx.moveTo(points[0].x, points[0].y);
      ctx.lineTo(points[1].x, points[1].y);
      ctx.lineTo(points[2].x, points[2].y);
      ctx.lineTo(points[3].x, points[3].y);
      ctx.closePath();
      ctx.lineWidth = borderWidth;
      ctx.strokeStyle = borderColor;
      ctx.stroke();

      // 绘制圆点
      ctx.beginPath();
      ctx.arc(centerX, centerY, dotSize, 0, Math.PI * 2);
      ctx.fillStyle = dotColor;
      ctx.fill();

      // 绘制文字
      if (text) {
        ctx.font = '12px Arial';
        ctx.textAlign = 'center';
        ctx.fillStyle = 'black';
        ctx.fillText(text, centerX, centerY + halfWidth + textOffset + borderWidth);
      }
    }

    var nodes = [
      { x: 100, y: 100, width: 30 },
      // { x: 200, y: 150, width: 30 },
      { x: 200, y: 200, width: 30 },
      // { x: 200, y: 250, width: 30 },
      { x: 200, y: 300, width: 30 },
      // { x: 200, y: 350, width: 30 },
      { x: 200, y: 400, width: 30 }
    ];

    // for (var i = 0; i < nodes.length; i++) {
    //   var node = nodes[i];
    //   drawDiamond({
    //     centerX: node.x,
    //     centerY: node.y,
    //     width: node.width,
    //     borderWidth: 1,
    //     borderColor: 'black',
    //     diamondColor: 'rgba(0,0,0,0)',
    //     dotColor: 'blue',
    //     dotSize: 2,
    //   });
    // }

    // ctx.quadraticCurveTo((startX + endX) / 2, startY - 50, endX, endY);// 先向上
    // ctx.quadraticCurveTo(startX, (startY + endY) / 2, endX, endY);// 先向下
    // ctx.quadraticCurveTo(startX, startY + (endY - startY) * 1.1, endX, endY);// 更曲线一点

    /* 
    canvas 写一个绘制曲线函数，要求参数为对象，可动态传入开始节点和结束节点的位置，曲线为向下弧度曲线，可支持定义颜色，并且曲线结束时要有一个人字形箭头(类似于计算符号大于号:>)，且方向自然一点

    */
    function drawCurveArrow({ startX, startY, endX, endY, curveColor, arrowColor }) {
      const canvas = document.getElementById('myCanvas');
      const ctx = canvas.getContext('2d');

      // 计算斜率
      const dx = endX - startX;
      const dy = endY - startY;
      const angle = Math.atan2(dy, dx);

      // 一个二次贝塞尔曲线，用于绘制曲线
      ctx.beginPath();
      ctx.moveTo(startX, startY);
      ctx.quadraticCurveTo(startX, startY + (endY - startY) * 1.05, endX, endY);// 1.05就是控制向上向下曲线的曲的程度
      ctx.strokeStyle = curveColor;
      ctx.lineWidth = 2;
      ctx.stroke();

      // 绘制曲线，将结束点的坐标转换为相对于中心点的坐标，然后根据方向角旋转画布。这样就可以在绘制曲线时，以中心点为原点，结束点为坐标系的原点。
      ctx.save();
      ctx.translate(endX, endY);
      ctx.rotate(angle);

      // 绘制箭头
      ctx.restore();
      ctx.beginPath();
      ctx.moveTo(endX, endY);
      ctx.lineTo(endX - 8, endY - 8);
      ctx.lineTo(endX - 5, endY);
      ctx.lineTo(endX - 8, endY + 8);
      ctx.closePath();
      ctx.fillStyle = arrowColor;
      ctx.fill();
      ctx.restore();

    }

    // for (let i = 1; i < nodes.length; i++) {
    //   drawCurveArrow({
    //     startX: nodes[0].x,
    //     startY: nodes[0].y,
    //     endX: nodes[i].x - nodes[i].width / 2,
    //     endY: nodes[i].y,
    //     curveColor: 'darkred',
    //     curveWidth: 3,
    //     arrowColor: 'darkred'
    //   });
    // }

    // canvas.addEventListener('mousemove', debounce(handleMousemove, 300));

    function handleMousemove(event) {
      var rect = canvas.getBoundingClientRect();
      var x = event.clientX - rect.left;
      var y = event.clientY - rect.top;

      for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];

        if (isWithinDiamond(x, y, node.x, node.y, node.width, node.width)) {
          console.log('i', i);
        }
      }
    }

  </script>

  <!-- 公共函数 -->
  <script>
    function debounce(func, delay) {
      let timeoutId;

      return function () {
        clearTimeout(timeoutId);

        timeoutId = setTimeout(() => {
          func.apply(this, arguments);
        }, delay);
      };
    }
    // 获取两个时间之间的时间段，并以数组形式返回，['2022-01', '2022-02', '2022-03']
    function calculateMonths(startDate, endDate) {
      const start = new Date(startDate);
      const end = new Date(endDate);
      // 计算相差的月份数
      const months = (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth()) + 1;
      // 创建存储月份的数组
      const monthArray = [];
      // 循环生成每个月份
      for (let i = 0; i < months; i++) {
        const date = new Date(start.getFullYear(), start.getMonth() + i, 1);
        const formattedDate = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2)
        monthArray.push(formattedDate);
      };
      return monthArray;
    }
    /* 
    js 写一个函数，参数为对象，输入某一天，计算在时间段内(年月日格式)的时间比例，要求计算到开始日期所在月的第一天，结束月的最后一天，如果不在时间段内直接返回1
    */
    function calculateTimeRatio(date, startDate, endDate) {
      date = new Date(date).getTime();
      startDate = new Date(startDate);
      endDate = new Date(endDate);
      const startOfMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1).getTime();
      const endOfMonth = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0).getTime();

      if (date <= startOfMonth) {
        return 0;
      }
      if (date >= endOfMonth) {
        console.log(379, date, startOfMonth, endOfMonth);
        return 1;
      }

      const totalDaysInMonth = endOfMonth / 1000 - startOfMonth / 1000;
      const daysPassed = date / 1000 - startOfMonth / 1000;
      return Number((daysPassed / totalDaysInMonth).toFixed(5));
    }

    /* 是否在菱形区域内
      centerX:菱形的中心点x坐标
      centerY:菱形的中心点y坐标
      diamondWidth:菱形的宽度
      diamondHeight: 菱形的高度
    */
    function isWithinDiamond(x, y, centerX, centerY, diamondWidth, diamondHeight) {
      var halfWidth = diamondWidth / 2;
      var halfHeight = diamondHeight / 2;

      // 计算菱形的四个顶点坐标
      var top = centerY - halfHeight;
      var bottom = centerY + halfHeight;
      var left = centerX - halfWidth;
      var right = centerX + halfWidth;

      var dx = Math.abs(x - centerX);
      var dy = Math.abs(y - centerY);

      // 检查点是否在菱形的上边界、下边界、左边界和右边界内
      var isInside = (dx / halfWidth + dy / halfHeight <= 1) &&
        (y >= top) && (y <= bottom) &&
        (x >= left) && (x <= right);

      return isInside;
    }
  </script>

  <!-- 主渲染函数 -->
  <script>
    let canvas = document.getElementById('myCanvas');
    let ctx = canvas.getContext('2d');
    const devicePixelRatio = window.devicePixelRatio || 1;
    // 定义通用参数
    let MilestoneOption = {
      canvasWidth: 200,
      canvasHeight: 200,
      Milestone_minTime: null,// 里程碑最小时间
      Milestone_maxTime: null,// 里程碑最大时间
      lineHeight: 40,// 表格线高
      lineWidth: 1,// 表格线宽
      lineNameWidth: 120,// 项目代号列的宽度
      timelineAreaHeight: 40,// 时间轴区域的高度
      timeLineInterval: 60,// 时间段间隔的宽度(表示一个月多宽)
      timeLineData: [],// 时间轴上的数据
      canvas_top: 20,// 顶部留出来多少间隙
      canvas_right: 20,// 右侧留出来多少间隙
      canvas_bottom: 20,// 底部留出来多少间隙
      canvas_left: 20,// 左侧留出来多少间隙
      blankWidth: 30,// 里程碑节点左右空白区(防止节点太靠近边框)
      diamond_width: 20,// 菱形的宽度
      diamond_border: 1,// 菱形边框
      timeLine_y: 0,// 时间轴线距离顶部的距离
    };
    // 定义图形宽高
    function DefineWidth(MilestoneData = []) {
      // 筛选出来最大最小时间
      MilestoneData.forEach(item => {
        let milestone = item.milestone || [];
        milestone.forEach(milestoneItem => {
          let time3 = new Date(milestoneItem.plantime);
          let time1 = new Date(MilestoneOption.Milestone_minTime || time3);
          let time2 = new Date(MilestoneOption.Milestone_maxTime || time3);

          MilestoneOption.Milestone_minTime = time1 > time3 ? time3 : time1;
          MilestoneOption.Milestone_maxTime = time3 > time2 ? time3 : time2;
        })
      })

      // 获取时间段
      MilestoneOption.timeLineData = calculateMonths(MilestoneOption.Milestone_minTime, MilestoneOption.Milestone_maxTime);
      // 计算宽度
      let width_1 = MilestoneOption.timeLineData.length * MilestoneOption.timeLineInterval;// 里程碑区域宽度  
      let width_2 = MilestoneOption.lineNameWidth;
      let width_3 = MilestoneOption.canvas_left + MilestoneOption.canvas_right;
      let width_4 = MilestoneOption.blankWidth * 2;// 两边预留的空白
      let needWidth = width_1 + width_2 + width_3 + width_4;
      // 计算高度
      let height_1 = MilestoneOption.canvas_top + MilestoneOption.canvas_bottom;// 上下间隙
      let height_2 = MilestoneOption.lineHeight * MilestoneData.length;// 表格高度
      let height_3 = MilestoneOption.timelineAreaHeight;// 底部时间轴区域的高度
      let needHeight = height_1 + height_2 + height_3;

      // 从新赋值宽高
      const curWidth = needWidth > canvas.width ? needWidth : canvas.width;
      const curHeight = needHeight > canvas.height ? needHeight : canvas.height;
      MilestoneOption.canvasWidth = curWidth;
      MilestoneOption.canvasHeight = curHeight;
      console.log(357, 'curWidth', curWidth, 'curHeight', curHeight);

      // 设置canvas的物理宽度和高度
      canvas.width = curWidth * devicePixelRatio;
      canvas.height = curHeight * devicePixelRatio;

      canvas.style.width = curWidth + 'px';
      canvas.style.height = curHeight + 'px';

      // 缩放绘图上下文，使其与物理像素一致
      ctx.scale(devicePixelRatio, devicePixelRatio);// 防止线条颜色失真

      // 设置背景色
      // ctx.fillStyle = 'yellowgreen';
      ctx.fillStyle = '#fff';
      // 绘制填充色矩形，覆盖整个 Canvas 区域
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
    // 定义时间轴
    function DefineTimeline(MilestoneData) {
      let dateLen = MilestoneOption.timeLineData.length;
      MilestoneOption.timeLine_y = MilestoneOption.canvasHeight - 30;
      const timeLine_y = MilestoneOption.timeLine_y;
      // 绘制直线
      ctx.beginPath();
      // 左侧开始位置
      const left_1 = MilestoneOption.canvas_left;
      const left_2 = MilestoneOption.lineNameWidth;
      const left_3 = MilestoneOption.blankWidth;
      const left_4 = left_1 + left_2 + left_3;// 左侧的距离
      const right_1 = left_4 + MilestoneOption.timeLineInterval * dateLen;
      ctx.moveTo(left_4, timeLine_y);
      ctx.lineTo(right_1, timeLine_y);
      ctx.lineWidth = 1;
      ctx.stroke();

      for (let i = 0; i <= dateLen; i++) {
        // 绘制标记点
        ctx.beginPath();
        let trick_x = left_4 + MilestoneOption.timeLineInterval * i;// 刻度的x位置
        ctx.moveTo(trick_x, timeLine_y);
        const width = 1;
        const height = 2;
        const color = 'black';
        // 设置填充颜色
        ctx.fillStyle = color;
        // 绘制矩形
        ctx.fillRect(trick_x, timeLine_y, width, height);

        if (i < dateLen) {
          // 绘制文字
          ctx.moveTo(trick_x + 10, timeLine_y + 10);
          ctx.font = '12px Arial';
          ctx.fillStyle = 'black';
          ctx.fillText(MilestoneOption.timeLineData[i], trick_x + 8, timeLine_y + 15);
        }
      }
    }
    // 绘制表格线条
    function DefineTableline(MilestoneData) {
      const dateLen = MilestoneData.length;
      let width_1 = MilestoneOption.timeLineData.length * MilestoneOption.timeLineInterval;// 里程碑区域宽度  
      let width_2 = MilestoneOption.lineNameWidth;
      let width_4 = MilestoneOption.blankWidth * 2;// 两边预留的空白
      const column_width = width_1 + width_2;// 列宽
      const column_heihgt = MilestoneOption.lineHeight * MilestoneData.length;// 列高
      const canvas_left = MilestoneOption.canvas_left;
      const canvas_top = MilestoneOption.canvas_top;
      // 绘制表格的行
      for (var i = 0; i < dateLen + 1; i++) {
        ctx.beginPath();
        const pos_left_y = MilestoneOption.canvas_top + MilestoneOption.lineHeight * i;
        ctx.moveTo(canvas_left, pos_left_y); // 行的起始点

        ctx.lineTo(canvas_left + width_1 + width_2 + width_4, pos_left_y); // 行的结束点
        ctx.stroke(); // 绘制行
      }

      // 列名的左边框
      const tableHeight = canvas_top + column_heihgt;
      ctx.beginPath();
      ctx.moveTo(canvas_left, canvas_top);
      ctx.lineTo(canvas_left, tableHeight);
      ctx.stroke();
      // 列名的右边框
      ctx.beginPath();
      ctx.moveTo(canvas_left + MilestoneOption.lineNameWidth, canvas_top);
      ctx.lineTo(canvas_left + MilestoneOption.lineNameWidth, tableHeight);
      ctx.stroke();
      // 表格整体的右侧
      ctx.beginPath();
      const table_right_x = canvas_left + width_1 + width_2 + width_4;
      ctx.moveTo(table_right_x, canvas_top);
      ctx.lineTo(table_right_x, tableHeight);
      ctx.stroke();
    }
    // 绘制项目代号
    function DrawProjectCode(MilestoneData) {
      const dateLen = MilestoneData.length;
      let width_1 = MilestoneOption.timeLineData.length * MilestoneOption.timeLineInterval;// 里程碑区域宽度  
      let width_2 = MilestoneOption.lineNameWidth;
      let width_4 = MilestoneOption.blankWidth * 2;// 两边预留的空白
      const column_width = width_1 + width_2;// 列宽
      const column_heihgt = MilestoneOption.lineHeight * MilestoneData.length;// 列高
      const canvas_left = MilestoneOption.canvas_left;
      const canvas_top = MilestoneOption.canvas_top;

      const fontSize = 16;
      const offset_y = MilestoneOption.lineHeight / 2 + fontSize / 2;
      ctx.beginPath();
      ctx.font = fontSize + 'px Arial';// 设置字体样式
      ctx.fillStyle = 'black';// 设置文字颜色

      // 绘制表格的行
      for (var i = 0; i < dateLen; i++) {
        const pos_left_y = MilestoneOption.canvas_top + MilestoneOption.lineHeight * i;
        let text = MilestoneData[i].projectSeq || '';
        const maxLength = 10;
        if (text.length > maxLength) {
          text = text.slice(0, maxLength) + '...';
        }
        ctx.fillText(text, canvas_left + 4, pos_left_y + offset_y);// 使用 fillText() 方法填充文字
      }
    }
    // 绘制里程碑时间线
    function DrawTimeLine(MilestoneData) { }
    // 绘制里程碑节点
    function DrawMilestone(MilestoneData) {
      MilestoneData.forEach((item, index) => {
        let milestone = item.milestone || [];
        if (milestone.length) {
          let time_start = null;// 里程碑开始时间
          let time_end = null;// 里程碑结束时间
          milestone.forEach(milestoneItem => {
            let time_3 = milestoneItem.plantime;
            time_start = time_3 < time_start ? time_3 : (time_start || time_3);
            time_end = time_3 > time_end ? time_3 : (time_end || time_3);
          })
          // 计算在整个时间轴上的比例
          let radio_start = calculateTimeRatio(time_start, MilestoneOption.Milestone_minTime, MilestoneOption.Milestone_maxTime);
          let radio_end = calculateTimeRatio(time_end, MilestoneOption.Milestone_minTime, MilestoneOption.Milestone_maxTime);
          // 计算纵向y的值
          let h_1 = MilestoneOption.canvas_top;
          let h_2 = MilestoneOption.lineHeight / 2;
          let pos_h_y = index * MilestoneOption.lineHeight + h_1 + h_2;// 线的y值
          // 画横向轴线
          let line_width = MilestoneOption.timeLineInterval * MilestoneOption.timeLineData.length;
          let left_fixed = 0;// 左侧固定的宽度
          let left_1 = MilestoneOption.canvas_left;
          let left_2 = MilestoneOption.lineNameWidth;
          let left_3 = MilestoneOption.blankWidth;// 左侧空白空隙
          let left_4 = line_width * radio_start;// 里程碑到左侧的距离
          let left_5 = line_width * (radio_end - radio_start);// 里程碑线的宽度
          let left_start = left_1 + left_2 + left_3 + left_4;// 起始点的位置
          let left_end = left_1 + left_2 + left_3 + left_4 + left_5;// 结束点的位置
          ctx.beginPath();
          ctx.moveTo(left_start, pos_h_y); // 设置起点坐标
          ctx.lineTo(left_end, pos_h_y); // 设置终点坐标
          ctx.strokeStyle = '#000'; // 设置线的颜色为红色
          ctx.stroke(); // 绘制线条
          // 计算每个里程碑位置
          let li_arr = [];// 里程碑节点比例的数组
          milestone.forEach(milestoneItem => {
            let time_3 = milestoneItem.plantime;
            let radio_li = calculateTimeRatio(time_3, MilestoneOption.Milestone_minTime, MilestoneOption.Milestone_maxTime);
            let li_offset_x = line_width * (radio_li - radio_start) + left_start;
            // 画出里程碑
            drawDiamond({
              centerX: li_offset_x,
              centerY: pos_h_y,
              width: MilestoneOption.diamond_width,
              borderWidth: MilestoneOption.diamond_border,
              borderColor: 'black',
              diamondColor: '#fff',
              dotColor: 'blue',
              dotSize: 2,
            });
          })
        }
      })
    }
    // 绘制当前时间线
    function DrawCurrentTimeLine(MilestoneData) {
      let radio_current = calculateTimeRatio(new Date(), MilestoneOption.Milestone_minTime, MilestoneOption.Milestone_maxTime);
      if (radio_current) {
        const canvas_top = MilestoneOption.canvas_top;
        let line_width = MilestoneOption.timeLineInterval * MilestoneOption.timeLineData.length;
        let left_fixed = 0;// 左侧固定的宽度
        let left_1 = MilestoneOption.canvas_left;
        let left_2 = MilestoneOption.lineNameWidth;
        let left_3 = MilestoneOption.blankWidth;// 左侧空白空隙

        let cur_time_x = left_1 + left_2 + left_3 + line_width * radio_current;
        // 绘制线条
        ctx.beginPath();
        ctx.moveTo(cur_time_x, canvas_top);
        ctx.lineTo(cur_time_x, MilestoneOption.timeLine_y);
        ctx.strokeStyle = 'red';
        ctx.stroke();
      }
    }
    // 绘制关联关系的箭头
    function DrawArrow(MilestoneData) { }
    // 悬浮节点给出提示
    function HoverTip(MilestoneData) { }

    // 初始化函数
    function initRender(MilestoneData = []) {
      DefineWidth(MilestoneData);// 定义图形宽高
      DefineTimeline(MilestoneData);// 定义时间轴
      DefineTableline(MilestoneData);// 绘制表格线条
      DrawProjectCode(MilestoneData);// 绘制项目代号
      DrawTimeLine(MilestoneData);// 绘制里程碑时间线
      DrawMilestone(MilestoneData);// 绘制里程碑节点
      DrawCurrentTimeLine(MilestoneData);// 绘制当前时间线
      DrawArrow(MilestoneData);// 绘制关联关系的箭头
      HoverTip(MilestoneData);// 悬浮节点给出提示
    }
    initRender(MilestoneData);
  </script>
</body>

</html>